<!-- // version 2020/05/19 -->
<!-- do not using script src, since the onBoard server can not operate at teh same time -->
<!-- pic in base64 url, 200px default (limited to 256px, as the coordinate is limited to u8)-->
<!-- // request must be limited in 26KB -->
<!-- // we assume that the length of header is less than 2K -->
<!-- // so the length of data is less than 24K = 24576 Bytes -->
<!-- // the length of points is limited in 12288  -->
<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" name="viewport" content="width=device-width, initial-scale=1.0">
    <title>designRuler</title>
    <style>
        * {
            font-family: Arial, 'Microsoft Yahei', Helvetica, sans-serif, 'Segoe UI', 'Sans';
            -ms-overflow-style: none;
            scrollbar-width: none;
        }

        *::-webkit-scrollbar {
            display: none;
        }
    </style>
</head>

<body style="background-color: #f4f2f4; margin: 0;">
    <div class="navbar" style="top:0;left:0;right:0;position:fixed;"></div>
    <div style="min-height: 48px;"></div>
    <!------------------------- start Content ----------------------------->
    <div class="rulerPage" style="padding:8px;"></div>
    <div class="picPage" style="padding:8px;"></div>
    <script> // navBar.js
        function navBar(navbarBox, opts) {
            // parse parameters ===================//
            if (typeof navbarBox == "string") navbarBox = document.querySelector(navbarBox);
            if (typeof opts == "undefined") opts = {};
            var title = ("title" in opts) ? opts.title : '';
            var titleColor = ("titleColor" in opts) ? 'color:' + opts.titleColor + ';' : 'color:#233;';
            var minItemWidth = ("minItemWidth" in opts) ? 'min-width:' + opts.minItemWidth + ';' : '';
            var navHeadImg = ("imageSrc" in opts) ? '<img src="' + opts.imageSrc
                + '" style="border-radius:6px;width:1.2em;height:1.2em;padding:0 0.2em">' : '';

            // init html and css ===================//
            { // navbarStyle
                var navbarStyle = '\
            __ID__ .navbar {background-color:#ffffff; box-shadow:6px 0px 6px #ddd;}                     \
            __ID__ .navHead {float:left;text-align:center;font-size:30px;padding:8px 24px;}             \
            __ID__ .navMenu {display: none; float: left; cursor: pointer; }                             \
            __ID__ .navTitle {margin:auto;font-weight:700;display:inline-block;text-decoration:none;}   \
            __ID__ .navList {display: block; text-align: right; padding: 8px; }                         \
            __ID__ .navList .item {cursor:pointer;display:inline-block;text-decoration:none;            \
                padding: 8px;font-size:16px;font-weight:600;text-align:center; }                        \
            __ID__ .navList .item:hover {background-color:#aaa; color:#fff !important;}                 \
            __ID__ .navList.sub { display: none; background: #f9f9f9; }                                 \
            @media screen and (max-width: 600px) {                                                      \
            __ID__ .navHead {float:none;}  __ID__ .navMenu {display:inline-block;}                      \
            __ID__ .navList {float:none;display:none;text-align:left;border-top:2px inset #dddddd;}     \
            __ID__ .navList .item {display:block;text-align:left;}}';
            }
            { //navbarHtml
                var menuIcon = '<div style="padding:0.15em 0.1em;box-sizing:content-box;">'
                    + '<div style="height:0.14em;width:0.8em;box-sizing:content-box;'
                    + 'border-top:0.14em solid #555;"></div>'
                    + '<div style="height:0.14em;width:0.8em;box-sizing:content-box;'
                    + 'border-bottom:0.14em solid #555;border-top:0.14em solid #555;"></div></div>';
                var navbarHtml = '<div class="navbar">'
                    + '<div class="navHead"><a class="navMenu">' + menuIcon + '</a><a class="navTitle"'
                    + 'href="#" style="' + titleColor + '">' + navHeadImg + title + '</a></div>'
                    + '<div class="mainList"></div><div class="subList"></div></div>';
            }
            function insertStyleHtml(styleText, htmlText, container, uniqueIDSign) {
                if (typeof uniqueIDSign == "undefined") uniqueIDSign = "__ID__";
                var uniqueID = "";
                while (1) {
                    uniqueID = ".ID" + Math.floor((new Date().getTime() * Math.random()));
                    if (document.querySelector(uniqueID) == null) break;
                }
                scopedStyle = styleText.replace(new RegExp(uniqueIDSign, "g"), uniqueID);
                if (scopedStyle.indexOf('<style') == -1) scopedStyle = '<style>' + scopedStyle + '</style>';
                container.className += " " + uniqueID.slice(1), container.innerHTML = scopedStyle + htmlText;
            }
            insertStyleHtml(navbarStyle, navbarHtml, navbarBox);

            // local vars =========================//
            var navbar = navbarBox.querySelector('.navbar');
            var navMenu = navbar.querySelector('.navMenu');
            var mainList = navbar.querySelector('.mainList');
            var subList = navbar.querySelector('.subList');

            // additional Events and Actions ======//
            navMenu.onclick = function () { hideNavList('.navList.main'); hideNavList('.navList.sub', true); };

            // local functions ====================//
            function hideNavList(Selector, hide) {

                function hideListCore(navList, hide) {
                    if (hide == "rev") navList.style.display = navList.style.display ? "" : "block";
                    else if (hide) navList.style.display = "";
                    else navList.style.display = "block";
                }

                if (typeof hide == "undefined") hide = "rev";
                var mainLists = mainList.querySelectorAll(Selector);
                var subLists = subList.querySelectorAll(Selector);
                var mainLength = mainLists.length, subLength = subLists.length;
                if (mainLength)
                    for (var i = 0; i < mainLength; i++) hideListCore(mainLists[i], hide);
                if (subLength)
                    for (var i = 0; i < subLength; i++) hideListCore(subLists[i], hide);
            }

            function genNavlist(isSub, members, color, name) {
                // members: [{text,idName,className,clickAct,minWidth}]
                if (typeof members == "undefined") members = [{}];
                var memberLength = members.length, memberList = [];
                var name = (typeof name == "undefined") ? '' : name;
                var color = (typeof color == "undefined") ? '' : 'color:' + color + ';';

                for (var i = 0; i < memberLength; i++) {
                    var mem = members[i], text, idName, className, inMinWidth;
                    text = ("text" in mem) ? mem.text : "";
                    idName = ("idName" in mem) ? mem.idName : "";
                    className = ("className" in mem) ? mem.className : "";
                    clickAct = ("clickAct" in mem) ? 'onclick="' + mem.clickAct + '";' : '';
                    inMinWidth = ("minWidth" in mem) ? 'min-width:' + mem.minWidth + ';"' : minItemWidth;
                    memberList.push('<a class="item ' + className + '" id="' + idName + '" '
                        + clickAct + '" style="' + color + inMinWidth + '" >' + text + '</a>');
                }

                if (isSub)
                    return '<div class="navList sub ' + name + '">\n' + memberList.join('\n') + '\n</div>';
                else
                    return '<div class="navList main">\n' + memberList.join('\n') + '\n</div>';
            }

            // methods ============================//
            this.hideNavList = hideNavList;
            this.queryItem = function (Selector) {
                var item = mainList.querySelector(Selector);
                if (!item) item = subList.querySelector(Selector);
                return item;
            }
            this.appendNavList = function (isSub, members, color, name) {
                // members: [{text,idName,className,minWidth}]
                if (isSub) subList.innerHTML += genNavlist(true, members, color, name);
                else mainList.innerHTML += genNavlist(false, members, color, name);
            }
        }

    </script>
    <script> // picPage.js
        function picPage(picBox, opts) {
            // parse parameters ================//
            if (typeof picBox == "string") picBox = document.querySelector(picBox);
            if (typeof opts == "undefined") opts = {};
            var basicSize = ("basicSize" in opts) ? opts.basicSize : "200px";
            var uploadIcon = ("uploadIcon" in opts) ? opts.uploadIcon : '';
            var backGray = ("backGray" in opts) ? opts.backGray : 255;
            var frontGray = ("frontGray" in opts) ? opts.frontGray : 255 - backGray;
            var changeWidth = ("changeWidth" in opts) ? opts.changeWidth : 600;
            var arrayStreamCallBack = ("arrayStreamCallBack" in opts) ? opts.arrayStreamCallBack : function () { };

            // init html and css ===============//
            { // picStyle
                var picStyle = '\
            __ID__ .leftcol {float: left; width: 21%;} __ID__ .rightcol {overflow: hidden;}\
            __ID__ .card {padding:12px;margin:12px 8px;overflow:hidden;\
                border-radius:6px;background-color:#fff; }\
            __ID__ .center {display:flex; flex-wrap:wrap; align-items:center; justify-content:center;}\
            __ID__ .card .title {font-size:20px;font-weight:600;;text-align:left; }\
            __ID__ .card .item {font-size:14px;font-weight:600;text-align:center;color:#666; }\
            __ID__ .card .info {font-size:16px;font-weight:600;text-align:center;overflow:auto;}\
            @media screen and (max-width: '+ changeWidth + 'px) {\
                __ID__ .leftcol, __ID__ .rightcol{float:none;width:100%;padding: 0;}}';
            }
            { // picHtml
                var picHtml = '<input type="file" class="pic" hidden="" />\
            <div class="leftcol" style="font-weight:600;">\
            <div class="card" style="text-align:center;">\
            <div class="title">Infos</div>\
            <div class="card" style="background:#fde;">\
            <div class="item">name</div><div class="info name">-</div></div>\
            <div class="card" style="background:#ffe;">\
            <div class="item">size</div><div class="info size"s>-</div></div>\
            </div><div class="card" style="text-align:center;">\
            <div class="card confirm" style="cursor:pointer;background:#ddf;">confirm</div>\
            </div></div>\
            <div class="rightcol">\
            <div class="card" style="text-align:center;">\
            <div class="title">Preview</div>\
            <div class="card center">\
            <div class="card upload" style="cursor:pointer;">\
            <div class="title" style="text-align:center;padding:1em;">Before</div>\
            <img class="image" style="width:1em;height:1em;margin:0.25em;display:block;"></img></div>\
            <div class="card">\
            <div class="title" style="text-align:center;padding:1em;">After</div>\
            <canvas class="canvas" style="width:1em;height:1em;margin:0.25em;display:block;"></canvas></div>\
            </div></div></div>';
            }
            function insertStyleHtml(styleText, htmlText, container, uniqueIDSign) {
                if (typeof uniqueIDSign == "undefined") uniqueIDSign = "__ID__";
                var uniqueID = "";
                while (1) {
                    uniqueID = ".ID" + Math.floor((new Date().getTime() * Math.random()));
                    if (document.querySelector(uniqueID) == null) break;
                }
                scopedStyle = styleText.replace(new RegExp(uniqueIDSign, "g"), uniqueID);
                if (scopedStyle.indexOf('<style') == -1) scopedStyle = '<style>' + scopedStyle + '</style>';
                container.className += " " + uniqueID.slice(1), container.innerHTML = scopedStyle + htmlText;
            }
            insertStyleHtml(picStyle, picHtml, picBox);

            // local vars =================//
            var mainView = picBox.querySelector('.rightcol .center');
            var inputPic = picBox.querySelector('input.pic');
            var picPageUp = picBox.querySelector('.upload');
            var confirm = picBox.querySelector('.confirm');
            var canvas = picBox.querySelector('canvas');
            var image = picBox.querySelector('img');
            var borderArrayStream;

            // addtional Events and Actions ========================//
            // image.src = uploadIcon;
            mainView.style.fontSize = basicSize;
            confirm.onclick = function () { arrayStreamCallBack(borderArrayStream); }
            picPageUp.onclick = function () { inputPic.click(); }
            picPageUp.ondrop = function (event) { getImage(event.dataTransfer.files[0], procImage); }
            inputPic.onchange = function () { getImage(this.files[0], procImage); }
            var clearDefault = function (event) { event.preventDefault(); };
            document.ondragenter = clearDefault;
            document.ondragover = clearDefault;
            document.ondrop = clearDefault;
            document.ondragleave = clearDefault;

            // local functions ==================//
            function getImage(file, procImage) {
                if (file.type.indexOf("image/") == -1) alert("invalid type");
                else {
                    picBox.querySelector('.name').innerText = file.name;
                    picBox.querySelector('.size').innerText = (file.size / 1024).toFixed(2) + 'KB';
                    procImage(canvas, image, URL.createObjectURL(file));
                }
            }
            function procImage(canvas, image, src) {
                var width = image.width, height = image.height;
                canvas.width = width, canvas.height = height;
                var ctx = canvas.getContext('2d');
                image.src = src;
                image.onload = function () {
                    ctx.drawImage(image, 0, 0, width, height);
                    var imageData = ctx.getImageData(0, 0, width, height);
                    imageData = rgb2gray(imageData);
                    var threshold = grayThresh(imageData);
                    imageData = gray2bit(imageData, threshold);
                    var borderArray = getBitBorderArray(imageData, frontGray, backGray);
                    border = formatBorderArray(borderArray);
                    imageData = genBorderImage(border.borderArray, imageData);
                    borderArrayStream = border.borderArrayStream;
                    console.log(border.borderArray);
                    ctx.putImageData(imageData, 0, 0);
                }
            }
            function rgb2gray(imageData) {
                var size = imageData.width * imageData.height;
                for (var i = 0; i < size; i++) {
                    var pos = i << 2;//4*i
                    var red = imageData.data[pos];
                    var green = imageData.data[pos + 1];
                    var blue = imageData.data[pos + 2];
                    var alpha = imageData.data[pos + 3];
                    if (alpha == 0) continue; // alpha=0: transparent
                    var gray = (0.299 * red + 0.587 * green + 0.114 * blue);
                    imageData.data[pos] = gray;
                    imageData.data[pos + 1] = gray;
                    imageData.data[pos + 2] = gray;
                }
                return imageData;
            }
            function grayThresh(imageData) {
                // otsu algorithm
                var histogram = new Uint32Array(256);
                var backPoiNum = new Uint32Array(256);
                var backTotalGrayDeg = new Uint32Array(256);
                var threshold = 0, variance = 0;
                var size = imageData.width * imageData.height; // some point cound be transparent
                var iSize = 0; // size of interest, not transparent   
                var pixels = imageData.data;
                for (var i = 0; i < size; i++) {
                    var pos = i << 2;
                    if (pixels[pos + 3] != 0) {// else transparent
                        iSize++;
                        histogram[pixels[pos]]++;
                    }
                }
                // get backgroundInfo
                backPoiNum[0] = histogram[0], backTotalGrayDeg[0] = 0;
                for (var i = 1; i < 256; i++) {
                    backPoiNum[i] = backPoiNum[i - 1] + histogram[i];
                    backTotalGrayDeg[i] = backTotalGrayDeg[i - 1] + histogram[i] * i;
                }
                // get variance between gray<thresh(back) and gray>=thresh(front)
                for (var tmpThresh = 0; tmpThresh < 256; tmpThresh++) {
                    var backPoiRate = backPoiNum[tmpThresh] / iSize;
                    var backAvgGrayDeg = backTotalGrayDeg[tmpThresh] / backPoiNum[tmpThresh];
                    var frontAvgGrayDeg = (backTotalGrayDeg[255] - backTotalGrayDeg[tmpThresh])
                        / (iSize - backPoiNum[tmpThresh]);
                    var tmpVariance = backPoiRate * (1 - backPoiRate)
                        * (backAvgGrayDeg - frontAvgGrayDeg) * (backAvgGrayDeg - frontAvgGrayDeg);
                    if (isNaN(frontAvgGrayDeg) || isNaN(tmpVariance)) tmpVariance = 0;
                    if (tmpVariance > variance) {
                        variance = tmpVariance;
                        threshold = tmpThresh;
                    }
                }
                // console.log(size, iSize, threshold, variance, histogram);
                return threshold;
            }
            function gray2bit(imageData, threshold) {
                var size = imageData.width * imageData.height;
                for (var i = 0; i < size; i++) {
                    var pos = i << 2;
                    if (imageData.data[pos + 3] == 0) continue; // transparent;
                    var gray = (imageData.data[pos] >= threshold) ? 255 : 0;
                    imageData.data[pos] = gray;
                    imageData.data[pos + 1] = gray;
                    imageData.data[pos + 2] = gray;
                }
                return imageData;
            }
            function getBitBorderArray(imageData, frontGray, backGray) {
                if (typeof frontGray == "undefined") frontGray = 0;
                if (typeof backGray == "undefined") backGray = 255;
                // moore algorithm
                var width = imageData.width, height = imageData.height;
                var pixels = imageData.data, size = width * height;
                var breakStart = 0, breakWidth = 0, breakHeight = 0;
                var borderArrayX = [], borderArrayY = [];
                var imageMark = new Uint8Array(size); // init:0, 0: unmarked, 1:marked, 2:start+marked
                // neighbor8: [[w,h],...] = [left->upLeft->upRight->...]
                var neighbor8 = [[-1, 0], [-1, -1], [0, -1], [1, -1], [1, 0], [1, 1], [0, 1], [-1, 1]];

                for (var h = 1; h < height - 1; h++) {
                    for (var w = 1; w < width - 1; w++) {
                        var cPos = (w + h * width) * 4;
                        if (pixels[cPos + 3] == 0) continue; // transparent
                        if (imageMark[cPos / 4]) continue; // have been marked
                        var center = pixels[cPos];
                        var left = pixels[cPos - 4];
                        if (center == frontGray && left == backGray) { // search around
                            var nPos = 0;
                            var b = [w, h], c = neighbor8[0];
                            while (1) {
                                var bPos = (b[0] + b[1] * width) * 4;
                                for (var i = 0; i < 8; i++, nPos++) {
                                    if (nPos > 7) nPos -= 8;
                                    c = neighbor8[nPos];
                                    var cPos = bPos + (c[0] + c[1] * width) * 4;
                                    if (pixels[cPos] == frontGray) {
                                        b = [b[0] + c[0], b[1] + c[1]];
                                        imageMark[b[0] + b[1] * width] = 1; // mark
                                        borderArrayX.push(b[0]), borderArrayY.push(b[1]);
                                        // get the last c (backGray)
                                        if (nPos == 2 || nPos == 3) nPos = 0; // left
                                        else if (nPos == 4 || nPos == 5) nPos = 2; // up
                                        else if (nPos == 6 || nPos == 7) nPos = 4; // right
                                        else if (nPos == 0 || nPos == 1) nPos = 6; // bottom
                                        c = neighbor8[nPos];
                                        break;
                                    }
                                }
                                if (b[0] == w && b[1] == h) { breakStart++; break; }
                                if (b[0] == 0 || b[0] == width - 1) { breakWidth++; break; }
                                if (b[1] == 0 || b[1] == height - 1) { breakHeight++; break; }
                            }
                        }
                    }
                }

                // console.log(breakStart, breakWidth, breakHeight);
                return { borderArrayX: borderArrayX, borderArrayY: borderArrayY };
            }
            function genBorderImage(borderArray, imageData) {
                var borderArrayX = borderArray.borderArrayX;
                var borderArrayY = borderArray.borderArrayY;
                var width = imageData.width, height = imageData.height;
                // reset image as white, visible
                var size = width * height;
                for (var i = 0; i < size; i++) {
                    var pos = i << 2;
                    imageData.data[pos] = 255;
                    imageData.data[pos + 1] = 255;
                    imageData.data[pos + 2] = 255;
                    imageData.data[pos + 3] = 255;
                }
                // draw border
                var borderPoiLength = borderArrayX.length;
                for (var i = 0; i < borderPoiLength; i++) {
                    var pos = (borderArrayX[i] + borderArrayY[i] * width) * 4;
                    imageData.data[pos] = 0;
                    imageData.data[pos + 1] = 0;
                    imageData.data[pos + 2] = 0;
                }

                return imageData;
            }
            function formatBorderArray(borderArray) {
                var newBorderArray = {borderArrayX:[], borderArrayY:[]};
                var borderArrayX = borderArray.borderArrayX;
                var borderArrayY = borderArray.borderArrayY;
                var length = borderArrayX.length;
                var step = 1;
                if (length > 12288) {
                    step = Math.floor(length / 12288) + 1;
                    length = Math.floor(length/step);
                } 
                var borderStreamLength = length * 2 + 14;
                var borderArrayStream = new Uint8Array(borderStreamLength);
                // borderArrayStream: [uint8_t:[DATA], uint32_t:length, uint16_t:x, uint16_t:y,...] 
                // (uint8_t) borderArrayStream: ['D','A','T','A', length[7:0], length[15:8], length[23:16],
                // length[31:24], x[7:0], x[15:8], y[7:0], y[15:8], x[7:0], x[15:8], y[7:0], y[15:8],....]
                borderArrayStream[0] = 'D'.charCodeAt();
                borderArrayStream[1] = 'A'.charCodeAt();
                borderArrayStream[2] = 'T'.charCodeAt();
                borderArrayStream[3] = 'A'.charCodeAt();
                borderArrayStream[4] = (length & 0x000000ff) >> 0;
                borderArrayStream[5] = (length & 0x0000ff00) >> 8;
                borderArrayStream[6] = (length & 0x00ff0000) >> 16;
                borderArrayStream[7] = (length & 0xff000000) >> 24;
                for (var i = 0; i < length; i++) {
                    var pos = 2 * i + 8;
                    var corPos = i * step;
                    borderArrayStream[pos + 0] = borderArrayX[corPos];
                    borderArrayStream[pos + 1] = borderArrayY[corPos];
                    newBorderArray.borderArrayX[i] = borderArrayX[corPos];
                    newBorderArray.borderArrayY[i] = borderArrayY[corPos];
                }
                borderArrayStream[2 * length + 8] = '\r'.charCodeAt();
                borderArrayStream[2 * length + 9] = '\n'.charCodeAt();
                borderArrayStream[2 * length + 10] = 'E'.charCodeAt();
                borderArrayStream[2 * length + 11] = 'N'.charCodeAt();
                borderArrayStream[2 * length + 12] = 'D'.charCodeAt();
                borderArrayStream[2 * length + 13] = '\0'.charCodeAt();
                return {borderArrayStream: borderArrayStream, borderArray: newBorderArray};
            }
            // methods =========================//
        }

    </script>
    <script>
        var nvb = new navBar(document.querySelector('.navbar'), {
            title: "designRuler", titleColor: "#cae", minItemWidth: "80px",
        });
        var picP = new picPage(document.querySelector('.picPage'), {
            changeWidth: 600, arrayStreamCallBack: function (arrayStream) {
                sendStream(arrayStream);
            }
        });

        // addtional Events and Actions =============================//
        nvb.appendNavList(false, [
            { text: "uploadPic", clickAct: "" },
            { text: "designRuler", clickAct: "window.open('./rulerPage.html', '_self');" },
        ], "#eac");
        document.querySelector('.rulerPage').style.display = "none";
        document.querySelector('.picPage').style.display = "";

        // local functions ==========================================//
        function sendStream(arrayStream) {
            var xhr = new XMLHttpRequest();
            xhr.open("POST", "/", true);
            xhr.setRequestHeader("Content-Type", "application/octet-stream");
            xhr.onreadystatechange = function () {
                if (xhr.readyState == 4 && (xhr.status == 200 || xhr.status == 304)) {
                    console.log(arrayStream);
                    var arrStr = Array.from(arrayStream).join(",");
                    console.log(arrStr);
                    var a = document.createElement('a');
                    var blob = new Blob([arrStr], {type: 'text/plain'});
                    a.href = URL.createObjectURL(blob);
                    a.download = "dataArray.txt";
                    a.click();
                }
            };
            xhr.send(arrayStream);
        }
    </script>
</body>

</html>